﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IdeaNMR.Shared
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T">Entry Content Type, 'string' is OK</typeparam>
    /// <typeparam name="U">Entry Level, 'int' is OK</typeparam>
    /// <typeparam name="V">Entry Type, some enum is good.</typeparam>
    public interface ILogBook<T, U, V>
    {
        U DefaultLevel
        {
            get;
            set;
        }

        void AddEntry(T message, V entryType);
        void AddEntry(T message, V entryType, U level);

        /// <summary>
        /// Reads all entries in the queue
        /// </summary>
        /// <returns></returns>
        T ReadEntry(out LogBookEntryType entryType, out int level);

        /// <summary>
        /// Removes all entries
        /// </summary>
        void Clear();

        /// <summary>
        /// Adds another log to this log so, that anothers log 'first' level will be atLevel in this log.
        /// </summary>
        /// <param name="log"></param>
        /// <param name="atLevel"></param>
        void AddLogBook(ILogBook<T, U, V> log, U atLevel);

        bool HasEntries { get; }
        bool HasErrors { get; }
    }

    public enum LogBookEntryType
    {
        General,
        Warning,
        Error,
        Status,
        Checkpoint
    }

    public class LogBook<T> : ILogBook<T, int, LogBookEntryType>
    {
        private class Entry
        {
            public T EntryContent
            { get; private set; }
            public int Level
            {
                get;
                private set;
            }
            public LogBookEntryType EntryType
            {
                get;
                private set;
            }

            public Entry(T msg, int level, LogBookEntryType lbt)
            {
                this.EntryContent = msg;
                this.Level = level;
                this.EntryType = lbt;
            }
        }

        private Queue<Entry> log;

        public List<T> Errors
        {
            get
            {
                List<T> result = (from entr in this.log
                                  where entr.EntryType == LogBookEntryType.Error
                                  select entr.EntryContent).ToList<T>();
                return result;
            }
        }

        #region ILogBook<LogBookEntry,int> Members
        private int defaultLevel;
        public int DefaultLevel
        {
            get
            {
                return this.defaultLevel;
            }
            set
            {
                if (value < 0) throw new ArgumentOutOfRangeException("DefaultLevel must be non negative.");
                this.defaultLevel = value;
            }
        }

        public void AddEntry(T message, LogBookEntryType entryType)
        {
            AddEntry(message, entryType, this.defaultLevel);
        }
        public void AddEntry(T message, LogBookEntryType entryType, int level)
        {
            Entry e = new Entry(message, level, entryType);
            this.log.Enqueue(e);
        }

        public T ReadEntry(out LogBookEntryType entryType, out int level)
        {
            Entry res = this.log.Dequeue();
            entryType = res.EntryType;
            level = res.Level;
            return res.EntryContent;
        }

        public void Clear()
        {
            this.log.Clear();
        }

        public void AddLogBook(ILogBook<T, int, LogBookEntryType> log, int atLevel)
        {
            LogBookEntryType lt;
            int lev;
            T msg;
            while (log.HasEntries)
            {
                msg = log.ReadEntry(out lt, out lev);
                Entry e = new Entry(msg, lev + atLevel, lt);
                this.log.Enqueue(e);
            }
        }

        public bool HasEntries
        {
            get
            {
                return this.log.Count>0;
            }
        }

        public bool HasErrors
        {
            get
            {
                return this.log.Any(x => x.EntryType == LogBookEntryType.Error);
            }
        }

        #endregion

        #region CONSTRUCTORS
        public LogBook()
        {
            this.log = new Queue<Entry>();
        }
        #endregion
    }
}
